/**
 * PrototypeMap
 *
 * @author Chris Pratt
 *
 * 9/24/2009
 */
package com.anodyzed.onyx.util;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("serial")
public class PrototypeMap<K,V> extends HashMap<K,Class<? extends V>> implements Serializable {

  /**
   * Constructs an empty map with the default initial capacity (16) and the 
   * default load factor (0.75). 
   */
  public PrototypeMap () {
    super();
  } //PrototypeMap

  /**
   * Constructor
   *
   * @param initialSize The Initial Size of the Map
   */
  public PrototypeMap (int initialSize) {
    super(initialSize);
  } //PrototypeMap

  /**
   * Constructor
   *
   * @param initialSize The Initial Size of the Map 
   * @param loadFactor The percent full before expanding the map 
   */
  public PrototypeMap (int initialSize,float loadFactor) {
    super(initialSize,loadFactor);
  } //PrototypeMap

  /**
   * Copy Constructor
   *
   * @param map The Map to Copy
   */
  public PrototypeMap (Map<? extends K,? extends Class<? extends V>> map) {
    super(map);
  } //PrototypeMap

  /**
   * Create a new Instance of the Class for the specified key
   *
   * @param key The Key
   * @return The new Instance of the Value
   */
  public V newInstance (K key) {
    try {
      Class<? extends V> klass = get(key);
      return (klass != null) ? klass.newInstance() : null;
    } catch(InstantiationException x) {
      throw new RuntimeException("Unable to create instance of " + key,x);
    } catch(IllegalAccessException x) {
      throw new RuntimeException("Unable to access " + key,x);
    }
  } //newInstance

  /**
   * Create a new Instance of the Class for the specified key using the 
   * constructor for the supplied types and passing the arguments into the 
   * constructor. 
   *
   * @param key The Key 
   * @param types The Constructor Type Array 
   * @param args The Constructor Arguments 
   * @return The new Instance of the Value
   */
  public V newInstance (K key,Class<?>[] types,Object[] args) {
    try {
      Class<? extends V> klass = get(key);
      return (klass != null) ? klass.getConstructor(types).newInstance(args) : null;
    } catch(InstantiationException x) {
      throw new RuntimeException("Unable to create instance of " + key,x);
    } catch(IllegalAccessException x) {
      throw new RuntimeException("Unable to access " + key,x);
    } catch(NoSuchMethodException x) {
      throw new RuntimeException("Unable to get constructor for " + key,x);
    } catch(InvocationTargetException x) {
      Throwable cause = x.getCause();
      if(cause instanceof RuntimeException) {
        throw (RuntimeException)cause;
      } else {
        throw new RuntimeException("Unable to instantiate " + key,cause);
      }
    }
  } //newInstance

  /**
   * Create a new Instance of the Class for the specified key passing in the 
   * supplied arguments to the constructor.
   *
   * @param key The Key 
   * @param args The Constructor Arguments 
   * @return The new Instance of the Value
   */
  public V newInstance (K key,Object... args) {
    try {
      Class<? extends V> klass = get(key);
      if(klass != null) {
        Class<?>[] types = new Class<?>[args.length];
        for(int i = 0;i < args.length;i++) {
          types[i] = (args[i] != null) ? args[i].getClass() : null;
        }
        return klass.getConstructor(types).newInstance(args);
      }
    } catch(InstantiationException x) {
      throw new RuntimeException("Unable to create instance of " + key,x);
    } catch(IllegalAccessException x) {
      throw new RuntimeException("Unable to access " + key,x);
    } catch(NoSuchMethodException x) {
      throw new RuntimeException("Unable to get constructor for " + key,x);
    } catch(InvocationTargetException x) {
      throw new RuntimeException("Unable to instantiate " + key,x.getTargetException());
    }
    return null;
  } //newInstance

} //*PrototypeMap
